#define _CRT_SECURE_NO_WARNINGS 1 // Prevents deprecation warning with MSVC
#include "jsontest.h"
#include <stdio.h>
#include <string>

#if defined(_MSC_VER)
// Used to install a report hook that prevent dialog on assertion and error.
# include <crtdbg.h>
#endif // if defined(_MSC_VER)

#if defined(_WIN32)
// Used to prevent dialog on memory fault.
// Limits headers included by Windows.h
# define WIN32_LEAN_AND_MEAN
# define NOSERVICE
# define NOMCX
# define NOIME
# define NOSOUND
# define NOCOMM
# define NORPC
# define NOGDI
# define NOUSER
# define NODRIVERS
# define NOLOGERROR
# define NOPROFILER
# define NOMEMMGR
# define NOLFILEIO
# define NOOPENFILE
# define NORESOURCE
# define NOATOM
# define NOLANGUAGE
# define NOLSTRING
# define NODBCS
# define NOKEYBOARDINFO
# define NOGDICAPMASKS
# define NOCOLOR
# define NOGDIOBJ
# define NODRAWTEXT
# define NOTEXTMETRIC
# define NOSCALABLEFONT
# define NOBITMAP
# define NORASTEROPS
# define NOMETAFILE
# define NOSYSMETRICS
# define NOSYSTEMPARAMSINFO
# define NOMSG
# define NOWINSTYLES
# define NOWINOFFSETS
# define NOSHOWWINDOW
# define NODEFERWINDOWPOS
# define NOVIRTUALKEYCODES
# define NOKEYSTATES
# define NOWH
# define NOMENUS
# define NOSCROLL
# define NOCLIPBOARD
# define NOICONS
# define NOMB
# define NOSYSCOMMANDS
# define NOMDI
# define NOCTLMGR
# define NOWINMESSAGES
# include <windows.h>
#endif // if defined(_WIN32)

namespace JsonTest
{


// class TestResult
// //////////////////////////////////////////////////////////////////

    TestResult::TestResult()
        : predicateId_( 1 )
        , lastUsedPredicateId_( 0 )
        , messageTarget_( 0 )
    {
        // The root predicate has id 0
        rootPredicateNode_.id_ = 0;
        rootPredicateNode_.next_ = 0;
        predicateStackTail_ = &rootPredicateNode_;
    }


    void
    TestResult::setTestName( const std::string &name )
    {
        name_ = name;
    }

    TestResult &
    TestResult::addFailure( const char *file, unsigned int line,
                            const char *expr )
    {
        /// Walks the PredicateContext stack adding them to failures_ if not already added.
        unsigned int nestingLevel = 0;
        PredicateContext *lastNode = rootPredicateNode_.next_;

        for ( ; lastNode != 0; lastNode = lastNode->next_ )
        {
            if ( lastNode->id_ > lastUsedPredicateId_ ) // new PredicateContext
            {
                lastUsedPredicateId_ = lastNode->id_;
                addFailureInfo( lastNode->file_, lastNode->line_, lastNode->expr_,
                                nestingLevel );
                // Link the PredicateContext to the failure for message target when
                // popping the PredicateContext.
                lastNode->failure_ = &( failures_.back() );
            }

            ++nestingLevel;
        }

        // Adds the failed assertion
        addFailureInfo( file, line, expr, nestingLevel );
        messageTarget_ = &( failures_.back() );
        return *this;
    }


    void
    TestResult::addFailureInfo( const char *file, unsigned int line,
                                const char *expr, unsigned int nestingLevel )
    {
        Failure failure;
        failure.file_ = file;
        failure.line_ = line;

        if ( expr )
        {
            failure.expr_ = expr;
        }

        failure.nestingLevel_ = nestingLevel;
        failures_.push_back( failure );
    }


    TestResult &
    TestResult::popPredicateContext()
    {
        PredicateContext *lastNode = &rootPredicateNode_;

        while ( lastNode->next_ != 0  &&  lastNode->next_->next_ != 0 )
        {
            lastNode = lastNode->next_;
        }

        // Set message target to popped failure
        PredicateContext *tail = lastNode->next_;

        if ( tail != 0  &&  tail->failure_ != 0 )
        {
            messageTarget_ = tail->failure_;
        }

        // Remove tail from list
        predicateStackTail_ = lastNode;
        lastNode->next_ = 0;
        return *this;
    }


    bool
    TestResult::failed() const
    {
        return !failures_.empty();
    }


    unsigned int
    TestResult::getAssertionNestingLevel() const
    {
        unsigned int level = 0;
        const PredicateContext *lastNode = &rootPredicateNode_;

        while ( lastNode->next_ != 0 )
        {
            lastNode = lastNode->next_;
            ++level;
        }

        return level;
    }


    void
    TestResult::printFailure( bool printTestName ) const
    {
        if ( failures_.empty() )
        {
            return;
        }

        if ( printTestName )
        {
            printf( "* Detail of %s test failure:\n", name_.c_str() );
        }

        // Print in reverse to display the callstack in the right order
        Failures::const_iterator itEnd = failures_.end();

        for ( Failures::const_iterator it = failures_.begin(); it != itEnd; ++it )
        {
            const Failure &failure = *it;
            std::string indent( failure.nestingLevel_ * 2, ' ' );

            if ( failure.file_ )
            {
                printf( "%s%s(%d): ", indent.c_str(), failure.file_, failure.line_ );
            }

            if ( !failure.expr_.empty() )
            {
                printf( "%s\n", failure.expr_.c_str() );
            }
            else if ( failure.file_ )
            {
                printf( "\n" );
            }

            if ( !failure.message_.empty() )
            {
                std::string reindented = indentText( failure.message_, indent + "  " );
                printf( "%s\n", reindented.c_str() );
            }
        }
    }


    std::string
    TestResult::indentText( const std::string &text,
                            const std::string &indent )
    {
        std::string reindented;
        std::string::size_type lastIndex = 0;

        while ( lastIndex < text.size() )
        {
            std::string::size_type nextIndex = text.find( '\n', lastIndex );

            if ( nextIndex == std::string::npos )
            {
                nextIndex = text.size() - 1;
            }

            reindented += indent;
            reindented += text.substr( lastIndex, nextIndex - lastIndex + 1 );
            lastIndex = nextIndex + 1;
        }

        return reindented;
    }


    TestResult &
    TestResult::addToLastFailure( const std::string &message )
    {
        if ( messageTarget_ != 0 )
        {
            messageTarget_->message_ += message;
        }

        return *this;
    }


    TestResult &
    TestResult::operator << ( bool value )
    {
        return addToLastFailure( value ? "true" : "false" );
    }


    TestResult &
    TestResult::operator << ( int value )
    {
        char buffer[32];
        sprintf( buffer, "%d", value );
        return addToLastFailure( buffer );
    }


    TestResult &
    TestResult::operator << ( unsigned int value )
    {
        char buffer[32];
        sprintf( buffer, "%u", value );
        return addToLastFailure( buffer );
    }


    TestResult &
    TestResult::operator << ( double value )
    {
        char buffer[32];
        sprintf( buffer, "%16g", value );
        return addToLastFailure( buffer );
    }


    TestResult &
    TestResult::operator << ( const char *value )
    {
        return addToLastFailure( value ? value
                                 : "<NULL>" );
    }


    TestResult &
    TestResult::operator << ( const std::string &value )
    {
        return addToLastFailure( value );
    }



// class TestCase
// //////////////////////////////////////////////////////////////////

    TestCase::TestCase()
        : result_( 0 )
    {
    }


    TestCase::~TestCase()
    {
    }


    void
    TestCase::run( TestResult &result )
    {
        result_ = &result;
        runTestCase();
    }



// class Runner
// //////////////////////////////////////////////////////////////////

    Runner::Runner()
    {
    }


    Runner &
    Runner::add( TestCaseFactory factory )
    {
        tests_.push_back( factory );
        return *this;
    }


    unsigned int
    Runner::testCount() const
    {
        return static_cast<unsigned int>( tests_.size() );
    }


    std::string
    Runner::testNameAt( unsigned int index ) const
    {
        TestCase *test = tests_[index]();
        std::string name = test->testName();
        delete test;
        return name;
    }


    void
    Runner::runTestAt( unsigned int index, TestResult &result ) const
    {
        TestCase *test = tests_[index]();
        result.setTestName( test->testName() );
        printf( "Testing %s: ", test->testName() );
        fflush( stdout );
#if JSON_USE_EXCEPTION

        try
        {
#endif // if JSON_USE_EXCEPTION
            test->run( result );
#if JSON_USE_EXCEPTION
        }
        catch ( const std::exception &e )
        {
            result.addFailure( __FILE__, __LINE__,
                               "Unexpected exception caugth:" ) << e.what();
        }

#endif // if JSON_USE_EXCEPTION
        delete test;
        const char *status = result.failed() ? "FAILED"
                             : "OK";
        printf( "%s\n", status );
        fflush( stdout );
    }


    bool
    Runner::runAllTest( bool printSummary ) const
    {
        unsigned int count = testCount();
        std::deque<TestResult> failures;

        for ( unsigned int index = 0; index < count; ++index )
        {
            TestResult result;
            runTestAt( index, result );

            if ( result.failed() )
            {
                failures.push_back( result );
            }
        }

        if ( failures.empty() )
        {
            if ( printSummary )
            {
                printf( "All %d tests passed\n", count );
            }

            return true;
        }
        else
        {
            for ( unsigned int index = 0; index < failures.size(); ++index )
            {
                TestResult &result = failures[index];
                result.printFailure( count > 1 );
            }

            if ( printSummary )
            {
                unsigned int failedCount = static_cast<unsigned int>( failures.size() );
                unsigned int passedCount = count - failedCount;
                printf( "%d/%d tests passed (%d failure(s))\n", passedCount, count, failedCount );
            }

            return false;
        }
    }


    bool
    Runner::testIndex( const std::string &testName,
                       unsigned int &indexOut ) const
    {
        unsigned int count = testCount();

        for ( unsigned int index = 0; index < count; ++index )
        {
            if ( testNameAt( index ) == testName )
            {
                indexOut = index;
                return true;
            }
        }

        return false;
    }


    void
    Runner::listTests() const
    {
        unsigned int count = testCount();

        for ( unsigned int index = 0; index < count; ++index )
        {
            printf( "%s\n", testNameAt( index ).c_str() );
        }
    }


    int
    Runner::runCommandLine( int argc, const char *argv[] ) const
    {
        typedef std::deque<std::string> TestNames;
        Runner subrunner;

        for ( int index = 1; index < argc; ++index )
        {
            std::string opt = argv[index];

            if ( opt == "--list-tests" )
            {
                listTests();
                return 0;
            }
            else if ( opt == "--test-auto" )
            {
                preventDialogOnCrash();
            }
            else if ( opt == "--test" )
            {
                ++index;

                if ( index < argc )
                {
                    unsigned int testNameIndex;

                    if ( testIndex( argv[index], testNameIndex ) )
                    {
                        subrunner.add( tests_[testNameIndex] );
                    }
                    else
                    {
                        fprintf( stderr, "Test '%s' does not exist!\n", argv[index] );
                        return 2;
                    }
                }
                else
                {
                    printUsage( argv[0] );
                    return 2;
                }
            }
            else
            {
                printUsage( argv[0] );
                return 2;
            }
        }

        bool succeeded;

        if ( subrunner.testCount() > 0 )
        {
            succeeded = subrunner.runAllTest( subrunner.testCount() > 1 );
        }
        else
        {
            succeeded = runAllTest( true );
        }

        return succeeded ? 0
               : 1;
    }


#if defined(_MSC_VER)
// Hook MSVCRT assertions to prevent dialog from appearing
    static int
    msvcrtSilentReportHook( int reportType, char *message, int *returnValue )
    {
        // The default CRT handling of error and assertion is to display
        // an error dialog to the user.
        // Instead, when an error or an assertion occurs, we force the
        // application to terminate using abort() after display
        // the message on stderr.
        if ( reportType == _CRT_ERROR  ||
                reportType == _CRT_ASSERT )
        {
            // calling abort() cause the ReportHook to be called
            // The following is used to detect this case and let's the
            // error handler fallback on its default behaviour (
            // display a warning message)
            static volatile bool isAborting = false;

            if ( isAborting )
            {
                return TRUE;
            }

            isAborting = true;

            fprintf( stderr, "CRT Error/Assert:\n%s\n", message );
            fflush( stderr );
            abort();
        }

        // Let's other reportType (_CRT_WARNING) be handled as they would by default
        return FALSE;
    }
#endif // if defined(_MSC_VER)


    void
    Runner::preventDialogOnCrash()
    {
#if defined(_MSC_VER)
        // Install a hook to prevent MSVCRT error and assertion from
        // popping a dialog.
        _CrtSetReportHook( &msvcrtSilentReportHook );
#endif // if defined(_MSC_VER)

        // @todo investiguate this handler (for buffer overflow)
        // _set_security_error_handler

#if defined(_WIN32)
        // Prevents the system from popping a dialog for debugging if the
        // application fails due to invalid memory access.
        SetErrorMode( SEM_FAILCRITICALERRORS
                      | SEM_NOGPFAULTERRORBOX
                      | SEM_NOOPENFILEERRORBOX );
#endif // if defined(_WIN32)
    }

    void
    Runner::printUsage( const char *appName )
    {
        printf(
            "Usage: %s [options]\n"
            "\n"
            "If --test is not specified, then all the test cases be run.\n"
            "\n"
            "Valid options:\n"
            "--list-tests: print the name of all test cases on the standard\n"
            "              output and exit.\n"
            "--test TESTNAME: executes the test case with the specified name.\n"
            "                 May be repeated.\n"
            "--test-auto: prevent dialog prompting for debugging on crash.\n"
            , appName );
    }



// Assertion functions
// //////////////////////////////////////////////////////////////////

    TestResult &
    checkStringEqual( TestResult &result,
                      const std::string &expected, const std::string &actual,
                      const char *file, unsigned int line, const char *expr )
    {
        if ( expected != actual )
        {
            result.addFailure( file, line, expr );
            result << "Expected: '" << expected << "'\n";
            result << "Actual  : '" << actual << "'";
        }

        return result;
    }


} // namespace JsonTest
